import tensorflow as tf
import pickle
import re
import numpy as np

# 常量定义
MAX_LENGTH = 50
NUM_LAYERS = 4
D_MODEL = 128
NUM_HEADS = 8
DFF = 512
DROPOUT_RATE = 0.1

@tf.keras.utils.register_keras_serializable()
class MultiHeadAttention(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads):
        super(MultiHeadAttention, self).__init__()
        self.num_heads = num_heads
        self.d_model = d_model

        assert d_model % self.num_heads == 0

        self.depth = d_model // self.num_heads

        self.wq = tf.keras.layers.Dense(d_model)
        self.wk = tf.keras.layers.Dense(d_model)
        self.wv = tf.keras.layers.Dense(d_model)

        self.dense = tf.keras.layers.Dense(d_model)

    def split_heads(self, x, batch_size):
        x = tf.reshape(x, (batch_size, -1, self.num_heads, self.depth))
        return tf.transpose(x, perm=[0, 2, 1, 3])

    def call(self, v, k, q, mask):
        batch_size = tf.shape(q)[0]

        q = self.wq(q)
        k = self.wk(k)
        v = self.wv(v)

        q = self.split_heads(q, batch_size)
        k = self.split_heads(k, batch_size)
        v = self.split_heads(v, batch_size)

        scaled_attention, attention_weights = scaled_dot_product_attention(
            q, k, v, mask)

        scaled_attention = tf.transpose(scaled_attention, perm=[0, 2, 1, 3])

        concat_attention = tf.reshape(scaled_attention,
                                    (batch_size, -1, self.d_model))

        output = self.dense(concat_attention)

        return output, attention_weights

    def get_config(self):
        config = super().get_config()
        config.update({
            "d_model": self.d_model,
            "num_heads": self.num_heads,
        })
        return config

def scaled_dot_product_attention(q, k, v, mask):
    matmul_qk = tf.matmul(q, k, transpose_b=True)

    dk = tf.cast(tf.shape(k)[-1], tf.float32)
    scaled_attention_logits = matmul_qk / tf.math.sqrt(dk)

    if mask is not None:
        scaled_attention_logits += (mask * -1e9)

    attention_weights = tf.nn.softmax(scaled_attention_logits, axis=-1)

    output = tf.matmul(attention_weights, v)

    return output, attention_weights

@tf.keras.utils.register_keras_serializable()
class EncoderLayer(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads, dff, rate=0.1):
        super(EncoderLayer, self).__init__()

        self.mha = MultiHeadAttention(d_model, num_heads)
        self.ffn = point_wise_feed_forward_network(d_model, dff)

        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)

        self.dropout1 = tf.keras.layers.Dropout(rate)
        self.dropout2 = tf.keras.layers.Dropout(rate)

    def call(self, x, training, mask):
        attn_output, _ = self.mha(x, x, x, mask)
        attn_output = self.dropout1(attn_output, training=training)
        out1 = self.layernorm1(x + attn_output)

        ffn_output = self.ffn(out1)
        ffn_output = self.dropout2(ffn_output, training=training)
        out2 = self.layernorm2(out1 + ffn_output)

        return out2

    def get_config(self):
        config = super().get_config()
        config.update({
            "d_model": self.d_model,
            "num_heads": self.num_heads,
            "dff": self.dff,
            "rate": self.rate,
        })
        return config

@tf.keras.utils.register_keras_serializable()
class DecoderLayer(tf.keras.layers.Layer):
    def __init__(self, d_model, num_heads, dff, rate=0.1):
        super(DecoderLayer, self).__init__()

        self.mha1 = MultiHeadAttention(d_model, num_heads)
        self.mha2 = MultiHeadAttention(d_model, num_heads)

        self.ffn = point_wise_feed_forward_network(d_model, dff)

        self.layernorm1 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm2 = tf.keras.layers.LayerNormalization(epsilon=1e-6)
        self.layernorm3 = tf.keras.layers.LayerNormalization(epsilon=1e-6)

        self.dropout1 = tf.keras.layers.Dropout(rate)
        self.dropout2 = tf.keras.layers.Dropout(rate)
        self.dropout3 = tf.keras.layers.Dropout(rate)

    def call(self, x, enc_output, training, look_ahead_mask, padding_mask):
        attn1, attn_weights_block1 = self.mha1(x, x, x, look_ahead_mask)
        attn1 = self.dropout1(attn1, training=training)
        out1 = self.layernorm1(attn1 + x)

        attn2, attn_weights_block2 = self.mha2(
            enc_output, enc_output, out1, padding_mask)
        attn2 = self.dropout2(attn2, training=training)
        out2 = self.layernorm2(attn2 + out1)

        ffn_output = self.ffn(out2)
        ffn_output = self.dropout3(ffn_output, training=training)
        out3 = self.layernorm3(ffn_output + out2)

        return out3, attn_weights_block1, attn_weights_block2

    def get_config(self):
        config = super().get_config()
        config.update({
            "d_model": self.d_model,
            "num_heads": self.num_heads,
            "dff": self.dff,
            "rate": self.rate,
        })
        return config

def point_wise_feed_forward_network(d_model, dff):
    return tf.keras.Sequential([
        tf.keras.layers.Dense(dff, activation='relu'),
        tf.keras.layers.Dense(d_model)
    ])

@tf.keras.utils.register_keras_serializable()
class Transformer(tf.keras.Model):
    def __init__(self, num_layers=NUM_LAYERS, d_model=D_MODEL, num_heads=NUM_HEADS, 
                 dff=DFF, input_vocab_size=None, target_vocab_size=None, rate=DROPOUT_RATE,
                 trainable=True, dtype=None):
        super().__init__(trainable=trainable, dtype=dtype)
        self.num_layers = num_layers
        self.d_model = d_model
        self.num_heads = num_heads
        self.dff = dff
        self.input_vocab_size = input_vocab_size
        self.target_vocab_size = target_vocab_size
        self.rate = rate

    def initialize_layers(self):
        """初始化所有层"""
        if self.input_vocab_size is None or self.target_vocab_size is None:
            raise ValueError("词汇表大小未设置")
            
        self.embedding = tf.keras.layers.Embedding(
            input_dim=self.input_vocab_size,
            output_dim=self.d_model,
            input_length=MAX_LENGTH
        )
        self.pos_encoding = positional_encoding(MAX_LENGTH, self.d_model)

        self.enc_layers = [EncoderLayer(self.d_model, self.num_heads, self.dff, self.rate)
                          for _ in range(self.num_layers)]

        self.dec_embedding = tf.keras.layers.Embedding(
            input_dim=self.target_vocab_size,
            output_dim=self.d_model,
            input_length=MAX_LENGTH
        )
        self.dec_pos_encoding = positional_encoding(MAX_LENGTH, self.d_model)

        self.dec_layers = [DecoderLayer(self.d_model, self.num_heads, self.dff, self.rate)
                          for _ in range(self.num_layers)]

        self.final_layer = tf.keras.layers.Dense(self.target_vocab_size)

    def call(self, inp, tar, enc_padding_mask, look_ahead_mask, dec_padding_mask, training=True):
        if not hasattr(self, 'embedding'):
            self.initialize_layers()
            
        enc_output = self.encode(inp, enc_padding_mask, training)
        dec_output, attention_weights = self.decode(
            tar, enc_output, look_ahead_mask, dec_padding_mask, training)
        final_output = self.final_layer(dec_output)
        return final_output, attention_weights

    def encode(self, x, mask, training):
        seq_len = tf.shape(x)[1]
        x = self.embedding(x)
        x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
        x += self.pos_encoding[:, :seq_len, :]

        for i in range(self.num_layers):
            x = self.enc_layers[i](x, training=training, mask=mask)

        return x

    def decode(self, x, enc_output, look_ahead_mask, padding_mask, training):
        seq_len = tf.shape(x)[1]
        attention_weights = {}

        x = self.dec_embedding(x)
        x *= tf.math.sqrt(tf.cast(self.d_model, tf.float32))
        x += self.dec_pos_encoding[:, :seq_len, :]

        for i in range(self.num_layers):
            x, block1, block2 = self.dec_layers[i](
                x, enc_output, 
                training=training,
                look_ahead_mask=look_ahead_mask, 
                padding_mask=padding_mask
            )
            attention_weights[f'decoder_layer{i+1}_block1'] = block1
            attention_weights[f'decoder_layer{i+1}_block2'] = block2

        return x, attention_weights

    def get_config(self):
        config = super().get_config()
        config.update({
            "num_layers": self.num_layers,
            "d_model": self.d_model,
            "num_heads": self.num_heads,
            "dff": self.dff,
            "input_vocab_size": self.input_vocab_size,
            "target_vocab_size": self.target_vocab_size,
            "rate": self.rate,
        })
        return config

    @classmethod
    def from_config(cls, config):
        base_config = {
            "num_layers": config.get("num_layers", NUM_LAYERS),
            "d_model": config.get("d_model", D_MODEL),
            "num_heads": config.get("num_heads", NUM_HEADS),
            "dff": config.get("dff", DFF),
            "input_vocab_size": config.get("input_vocab_size", None),
            "target_vocab_size": config.get("target_vocab_size", None),
            "rate": config.get("rate", DROPOUT_RATE),
        }
        
        if "trainable" in config:
            base_config["trainable"] = config["trainable"]
        if "dtype" in config:
            base_config["dtype"] = config["dtype"]
            
        return cls(**base_config)

def get_angles(pos, i, d_model):
    angle_rates = 1 / np.power(10000, (2 * (i//2)) / np.float32(d_model))
    return pos * angle_rates

def positional_encoding(position, d_model):
    angle_rads = get_angles(np.arange(position)[:, np.newaxis],
                           np.arange(d_model)[np.newaxis, :],
                           d_model)

    angle_rads[:, 0::2] = np.sin(angle_rads[:, 0::2])
    angle_rads[:, 1::2] = np.cos(angle_rads[:, 1::2])

    pos_encoding = angle_rads[np.newaxis, ...]

    return tf.cast(pos_encoding, dtype=tf.float32)

def preprocess_sentence(sentence):
    sentence = sentence.lower().strip()
    # 对于英文句子添加空格
    sentence = re.sub(r"([?.!,])", r" \1 ", sentence)
    sentence = re.sub(r'[" "]+', " ", sentence)
    sentence = re.sub(r"[^a-zA-Z?.!,]+", " ", sentence)
    sentence = sentence.strip()
    return sentence

def create_padding_mask(seq):
    seq = tf.cast(tf.math.equal(seq, 0), tf.float32)
    return seq[:, tf.newaxis, tf.newaxis, :]

def create_look_ahead_mask(size):
    mask = 1 - tf.linalg.band_part(tf.ones((size, size)), -1, 0)
    return mask

def create_masks(inp, tar):
    inp = tf.cast(inp, tf.int32)
    tar = tf.cast(tar, tf.int32)
    
    enc_padding_mask = create_padding_mask(inp)
    dec_padding_mask = create_padding_mask(inp)
    look_ahead_mask = create_look_ahead_mask(tf.shape(tar)[1])
    dec_target_padding_mask = create_padding_mask(tar)
    combined_mask = tf.maximum(dec_target_padding_mask, look_ahead_mask)
    
    return enc_padding_mask, combined_mask, dec_padding_mask

def sample_next_token(predictions, temperature=0.7):
    """使用温度采样来选择下一个词"""
    # 调整预测的形状为 [batch_size, vocab_size]
    predictions = tf.squeeze(predictions, axis=1)  # 移除中间的维度
    
    # 应用温度
    predictions = predictions / temperature
    
    # 将预测转换为概率分布
    probs = tf.nn.softmax(predictions, axis=-1)
    
    # 从概率分布中采样
    return tf.random.categorical(tf.math.log(probs), num_samples=1)

def translate_text(model, sentence, input_tokenizer, target_tokenizer, 
                  temperature=0.7, max_length=50, repetition_penalty=1.2):
    """改进的翻译函数"""
    # 预处理输入句子
    sentence = preprocess_sentence(sentence)
    
    # 将输入句子转换为张量
    inputs = [input_tokenizer.word_index.get(word, input_tokenizer.word_index['<unk>']) 
              for word in sentence.split()]
    inputs = tf.keras.preprocessing.sequence.pad_sequences([inputs], maxlen=MAX_LENGTH, padding='post')
    inputs = tf.convert_to_tensor(inputs, dtype=tf.int32)
    
    # 初始化解码器输入
    output = tf.convert_to_tensor([[target_tokenizer.word_index['<start>']]], dtype=tf.int32)
    
    result = []
    generated_tokens = set()  # 用于跟踪已生成的词
    
    for i in range(max_length):
        enc_padding_mask, combined_mask, dec_padding_mask = create_masks(inputs, output)
        
        # 预测下一个词
        predictions, _ = model(
            inputs, 
            output,
            enc_padding_mask,
            combined_mask,
            dec_padding_mask,
            training=False
        )
        
        # 获取最后一个词的预测
        predictions = predictions[:, -1:, :]
        
        # 应用重复惩罚
        for token_id in generated_tokens:
            predictions = tf.tensor_scatter_nd_update(
                predictions,
                [[0, 0, token_id]],
                [predictions[0, 0, token_id] / repetition_penalty]
            )
            
        # 使用温度采样选择下一个词
        predicted_id = sample_next_token(predictions, temperature)
        predicted_id = tf.cast(predicted_id[0], dtype=tf.int32)  # 修改这里
        
        # 将预测的词添加到结果中
        predicted_word = target_tokenizer.index_word.get(int(predicted_id), '<unk>')
        
        # 如果预测到特殊标记或重复次数过多，就停止预测
        if predicted_word in ['<start>', '<end>', '<unk>']:
            break
            
        # 检查是否有过多重复
        if len(result) >= 3 and all(w == predicted_word for w in result[-3:]):
            break
            
        result.append(predicted_word)
        generated_tokens.add(int(predicted_id))
        
        # 将预测的词添加到输出中以继续生成
        # 确保维度匹配
        predicted_id = tf.reshape(predicted_id, [1, 1])  # 修改这里
        output = tf.concat([output, predicted_id], axis=1)
        
        # 如果生成的句子太长，就停止
        if len(result) >= max_length:
            break
    
    # 后处理：移除重复的短语
    final_result = []
    prev_phrase = ""
    for word in result:
        current_phrase = prev_phrase + word
        if current_phrase not in "".join(final_result):
            final_result.append(word)
            prev_phrase = word
    
    return "".join(final_result)

def main():
    try:
        # 首先加载分词器以获取词汇表大小
        print("正在加载分词器...")
        with open('input_tokenizer.pickle', 'rb') as handle:
            input_tokenizer = pickle.load(handle)
        with open('target_tokenizer.pickle', 'rb') as handle:
            target_tokenizer = pickle.load(handle)
            
        # 设置词汇表大小
        input_vocab_size = len(input_tokenizer.word_index) + 1
        target_vocab_size = len(target_tokenizer.word_index) + 1
        
        # 加载模型
        print("正在加载模型...")
        custom_objects = {
            'Transformer': Transformer,
            'MultiHeadAttention': MultiHeadAttention,
            'EncoderLayer': EncoderLayer,
            'DecoderLayer': DecoderLayer,
        }
        
        # 创建新的模型实例
        model = Transformer(
            num_layers=NUM_LAYERS,
            d_model=D_MODEL,
            num_heads=NUM_HEADS,
            dff=DFF,
            input_vocab_size=input_vocab_size,
            target_vocab_size=target_vocab_size,
            rate=DROPOUT_RATE
        )
        
        # 加载权重
        saved_model = tf.keras.models.load_model('transformer_model.keras', 
                                               custom_objects=custom_objects)
        model.set_weights(saved_model.get_weights())
        
        # 初始化层
        model.initialize_layers()
        
        print("模型加载成功！\n")
        
        # 测试翻译
        test_sentences = [
            "Hello, how are you?",
            "I am fine, thank you.",
            "What's your name?",
            "Nice to meet you.",
            "Where are you from?",
            "I love programming"
        ]
        
        print("开始翻译测试：")
        print("-" * 50)
        for sentence in test_sentences:
            translation = translate_text(
                model, 
                sentence, 
                input_tokenizer, 
                target_tokenizer,
                temperature=0.7,  # 可以调整这个值
                max_length=30,    # 限制输出长度
                repetition_penalty=1.2  # 可以调整这个值
            )
            print(f"英文: {sentence}")
            print(f"中文: {translation}")
            print("-" * 50)
            
        # 交互式翻译
        print("\n现在你可以输入任何英文句子进行翻译（输入 'q' 退出）：")
        while True:
            user_input = input("\n请输入英文句子: ")
            if user_input.lower() == 'q':
                break
                
            translation = translate_text(
                model, 
                user_input, 
                input_tokenizer, 
                target_tokenizer,
                temperature=0.7,  # 可以调整这个值
                max_length=30,    # 限制输出长度
                repetition_penalty=1.2  # 可以调整这个值
            )
            print(f"翻译结果: {translation}")
            
    except Exception as e:
        print(f"发生错误: {str(e)}")

if __name__ == "__main__":
    main()